package cn._2dland.websocket.io;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import cn._2dland.websocket.model.message.BinaryMessage;
import cn._2dland.websocket.model.message.ControlMessage;
import cn._2dland.websocket.model.message.Message;
import cn._2dland.websocket.model.message.MessageTypesV08;
import cn._2dland.websocket.model.message.TextMessage;

/**
 * for hybi ver. 8.0~17.0
 * 
 * @author deadblue
 */
public class MessageInputStreamV08 extends MessageInputStream {
	
	public MessageInputStreamV08(InputStream in) {
		super(in);
	}

	/**
	 * 读取一个字节，当返回负值时，抛出套接字关闭异常
	 * 
	 * @return
	 * @throws IOException 标准IO异常，从输入流读取数据产生的异常，直接向上抛出
	 * @throws SocketClosedException 套接字关闭异常，当读取到一个小于零的字节值时，抛出该异常
	 */
	private int readByte() throws IOException, SocketClosedException {
		int b = in.read();
		if(b < 0) throw new SocketClosedException();
		
		return b;
	}

	@Override
	public Message readMessage() throws IOException {
		// 消息对象
		Message msg = null;

		try {
			// 准备缓冲区
			ByteArrayOutputStream buf = new ByteArrayOutputStream();

			// 对于被分帧的消息，循环读取直到完成
			// TODO: 先这么搞吧，以后改成分帧读取，因为规范中说数据帧中间可以插控制帧，但是一般不会发生这种情况
			boolean isFin = false;
			byte msgType = MessageTypesV08.TYPE_UNKNOWN;
			
			// 开始读取（这段有点长……
			while(!isFin) {
				// 读取报文头
				int msgHead = readByte();
				// 报文是否为结束报文
				isFin = (msgHead & 0x80) != 0;
				// 报文类型
				if(msgType == MessageTypesV08.TYPE_UNKNOWN)
					msgType = (byte)(msgHead & 0x0f);

				// 读取数据头
				int dataHead = in.read();
				// 是否启用掩码
				boolean isMask = (dataHead & 0x80) != 0;
				// 数据长度
				long dataLen = dataHead & 0x7f;
				if(dataLen == 126 || dataLen == 127) {
					// dataLen为126或127时，使用后面2个或8个字节计算数据长度
					int count = (dataLen == 126) ? 2 : 8;
					// 读取按BigEndian存储的长度
					dataLen = 0;
					for(int i = 0; i < count; i++) {
						dataLen = dataLen << 8 | in.read();
					}
				}

				// 存储掩码（如果有）
				byte[] masks = null;
				if(isMask) {
					// 若启用掩码，接下来的四字节为掩码
					masks = new byte[4];
					for(int i = 0; i < 4; i++) {
						masks[i] = (byte) in.read();
					}
				}

				// 开始读取数据，若有掩码则进行对应解码
				for(int i = 0; i < dataLen; i++) {
					byte b = (byte) in.read();
					// 若有掩码，进行解码
					if(isMask) 
						b ^= masks[i % 4];
					// 写入缓冲区
					buf.write(b);
				}
			}
			buf.flush();
			buf.close();

			// 读取完成，构造消息对象
			switch(msgType) {
			case MessageTypesV08.TYPE_DATA_BINARY:
				msg = new BinaryMessage(buf.toByteArray());
				break;
			case MessageTypesV08.TYPE_DATA_TEXT:
				String text = new String(buf.toByteArray(), "utf-8");
				msg = new TextMessage(text);
				break;
			case MessageTypesV08.TYPE_CTRL_PING:
				msg = new ControlMessage(ControlMessage.PING);
				break;
			case MessageTypesV08.TYPE_CTRL_PONG:
				msg = new ControlMessage(ControlMessage.PONG);
				break;
			case MessageTypesV08.TYPE_CTRL_CLOSE:
			case MessageTypesV08.TYPE_UNKNOWN:
			default:
				msg = new ControlMessage(ControlMessage.CLOSE);
			}
		} catch (SocketClosedException e) {
			// 当发生套接字关闭时，直接返回关闭消息，让线程安全退出
			msg = new ControlMessage(ControlMessage.CLOSE);
		}

		return msg;
	}

}

/**
 * 套接字关闭异常
 * 
 * @author deadblue
 */
class SocketClosedException extends Exception {

	private static final long serialVersionUID = 1L;

	public SocketClosedException() {
		super("Socket has closed!");
	}

}